/*************************************************************************
 *                     COPYRIGHT NOTICE
 *            Copyright 2024 Horizon Robotics, Inc.
 *                   All rights reserved.
 *************************************************************************/
#include <algorithm>
#include <errno.h>
#include <fcntl.h>
#include <iostream>
#include <istream>
#include <sstream>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <gbm.h>
#include <cassert>
#include <fstream>

#include "gles_winsys.hpp"
#include "gles_debug.hpp"

typedef struct dma_buf_sync_s
{
	uint64_t flags;
} dma_buf_sync_t;

#define DMA_BUF_BASE 'b'
#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync_s)
#define DMA_BUF_SYNC_READ (1 << 0)
#define DMA_BUF_SYNC_WRITE (2 << 0)
#define DMA_BUF_SYNC_RW (DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE)
#define DMA_BUF_SYNC_START (0 << 2)
#define DMA_BUF_SYNC_END (1 << 2)

const char *default_drm_device = "/dev/dri/card0";

egl_winsys::egl_winsys(OptionParser &optionParser)
	: win_options			(optionParser)
	, egl_config			(NULL)
	, egl_display			(EGL_NO_DISPLAY)
	, egl_window			(NULL)
	, egl_context			(EGL_NO_CONTEXT)
	, egl_surface			(EGL_NO_SURFACE)
	, glEGLImageTargetTexture2DOES	(nullptr)
{
	win_w = win_options.m_options[OptionParser::WIN_WIDTH].val;
	win_h = win_options.m_options[OptionParser::WIN_HEIGHT].val;
	win_out_type  = static_cast<WinSysOutType>(win_options.m_options[OptionParser::WINSYS_OUT_TYPE].val);
	out_image_plane_num = win_out_type == WinSysOutType::WIN_OUT_NV12 ? 2 : 1;

	std::cout << "egl_winsys() win_w=" << win_w << " win_h=" << win_h << " win_out_type=" << (uint)win_out_type << std::endl;
}

void egl_winsys::make_draw_current(void)
{
	EGLBoolean ret;
	eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
	ret = eglSwapInterval(egl_display, 1);	// 垂直同步， 第二个参数的意思：等待多少次VSync信号后，eglSwapBuffer可以继续执行。

	std::cout << "make_draw_current() turn off vsync: " << (ret == EGL_TRUE ? "success" : "failed") << std::endl;
}

void egl_winsys::swap(void)
{
	int32_t ret = RUN_SUCCESS;

	EGLBoolean res = eglSwapBuffers(egl_display, egl_surface);
	assert(res == true);
}

bool egl_winsys::wait(void)
{
    return false;
}

int32_t egl_winsys::set_gles_debug_output_context(void)
{
	int32_t flags;

	make_draw_current();
	glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
	if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
		glEnable(GL_DEBUG_OUTPUT);
		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
		glDebugMessageCallback(glDebugContexOutput, nullptr);
		glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
	} else {
		std::cout << "Failed to set gles debug context!" << std::endl;
		return -INIT_FAILED;
	}

	return INIT_SUCCESS;
}

void *egl_gbm_drm_init::user_data_ = nullptr;

egl_gbm_drm_init::egl_gbm_drm_init(OptionParser &optionParser)
	: egl_winsys(optionParser)
	, gbm_device		(nullptr)
	, drm_fd		(-1)
	, crtc_set_		(false)
	, resources_		(nullptr)
	, connector_		(nullptr)
	, encoder_		(nullptr)
	, crtc_			(NULL)
	, mode_			(nullptr)
	, pending_bo_		(nullptr)
	, flipped_bo_		(nullptr)
	, presented_bo_		(nullptr)
{
	enable_drm_ = static_cast<WinSysType>(optionParser.m_options[OptionParser::WINSYS_TYPE].val) == WinSysType::DRM ? true : false;
	user_data_ = (void *)this;

	std::cout << "egl_gbm_drm_init() enable_drm?" << enable_drm_ << std::endl;
}

egl_gbm_drm_init::~egl_gbm_drm_init(void)
{
}

int32_t egl_gbm_drm_init::init()
{
	int32_t ret = INIT_SUCCESS;
	EGLBoolean res;
	EGLint num_configs;

	eglBindAPI(EGL_OPENGL_ES_API);
	ASSERT_TRUE_WITH_RETURN_VAL(eglGetError() == EGL_SUCCESS, -INIT_FAILED);


	drm_fd = open(default_drm_device, O_RDWR);
	ASSERT_TRUE_WITH_RETURN_VAL(drm_fd >= 0, -INIT_FAILED);

	ret = gbm_device_create();		// 生成gbm设备,表示一个GPU设备
	ASSERT_TRUE_WITH_RETURN_VAL(ret == INIT_SUCCESS, -INIT_FAILED);

	// EGL初始化
	egl_display = eglGetDisplay((EGLNativeDisplayType)gbm_device);   // win32上gbm_device更换成hdc
	ASSERT_TRUE_WITH_RETURN_VAL(egl_display != EGL_NO_DISPLAY, -INIT_FAILED);
	res = eglInitialize(egl_display, NULL, NULL);
	ASSERT_TRUE_WITH_RETURN_VAL(res == EGL_TRUE, -INIT_FAILED);

	// EGL配置
	res = eglChooseConfig(egl_display, getWinConfigsPtr(), &egl_config, 1, &num_configs);
	ASSERT_TRUE_WITH_RETURN_VAL(res == EGL_TRUE, -INIT_FAILED);
	ASSERT_TRUE_WITH_RETURN_VAL(num_configs == 1, -INIT_FAILED);

	ret = egl_create_window();
	ASSERT_TRUE_WITH_RETURN_VAL(ret == INIT_SUCCESS, -INIT_FAILED);

	// EGL上下文
	egl_context = eglCreateContext(egl_display, egl_config, EGL_NO_CONTEXT, context_attribs);
	ASSERT_TRUE_WITH_RETURN_VAL(egl_context != EGL_NO_CONTEXT, -INIT_FAILED);

	// EGL表面
	egl_surface = eglCreateWindowSurface(egl_display, egl_config, egl_window, NULL);			// win32上egl_window更换成hdc
	ASSERT_TRUE_WITH_RETURN_VAL(egl_surface != EGL_NO_SURFACE, -INIT_FAILED);


	return INIT_SUCCESS;
}

void egl_gbm_drm_init::de_init()
{
	EGLBoolean res;

	if (egl_display != EGL_NO_DISPLAY) {
		res = eglMakeCurrent(egl_display, EGL_NO_SURFACE,
					EGL_NO_SURFACE, EGL_NO_CONTEXT);
		CHECK(res == EGL_TRUE);
	}

	if (egl_display != EGL_NO_DISPLAY && egl_context != EGL_NO_CONTEXT) {
		res = eglDestroyContext(egl_display, egl_context);
		CHECK(res == EGL_TRUE);
	}

	if (egl_display != EGL_NO_DISPLAY && egl_surface != EGL_NO_SURFACE) {
		res = eglDestroySurface(egl_display, egl_surface);
		CHECK(res == EGL_TRUE);
	}

	if (egl_display != EGL_NO_DISPLAY) {
		res = eglTerminate(egl_display);
		CHECK(res == EGL_TRUE);
	}

	if (egl_window != NULL)
		gbm_surface_destroy((struct gbm_surface *)egl_window);

	res = eglReleaseThread();
	CHECK(res == EGL_TRUE);

	if (gbm_device) {
		gbm_device_destroy(gbm_device);
		gbm_device = nullptr;
	}

	if (connector_) {
		drmModeFreeCrtc(crtc_);
		crtc_ = nullptr;
	}

	if (connector_) {
		drmModeFreeConnector(connector_);
		connector_ = nullptr;
	}
	if (encoder_) {
		drmModeFreeEncoder(encoder_);
		encoder_ = nullptr;
	}
	if (resources_) {
		drmModeFreeResources(resources_);
		resources_ = nullptr;
	}

	if (drm_fd >= 0) {
		close(drm_fd);
		drm_fd = -1;
	}
}

int32_t egl_gbm_drm_init::gbm_device_create(void)
{
	gbm_device = gbm_create_device(drm_fd);
	ASSERT_TRUE_WITH_RETURN_VAL(gbm_device != nullptr, -INIT_FAILED);
	return INIT_SUCCESS;
}

int32_t egl_gbm_drm_init::egl_create_window(void)
{
	EGLBoolean egl_res;
	EGLint egl_native_id;

	egl_res = eglGetConfigAttrib(egl_display, egl_config,
			EGL_NATIVE_VISUAL_ID, &egl_native_id);
	ASSERT_TRUE_WITH_RETURN_VAL(egl_res == EGL_TRUE, -INIT_FAILED);

	egl_window = gbm_surface_create(gbm_device, win_w, win_h, egl_native_id,
							     GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
	ASSERT_TRUE_WITH_RETURN_VAL(egl_window != nullptr, -INIT_FAILED);

	return INIT_SUCCESS;
}

/**
 * @brief 将前缓冲打印成一张图片
 */
int32_t egl_gbm_drm_init::dump_one_image()
{
	std::cout << "dump_one_image()" << std::endl;

	struct gbm_bo *front_bo;
	int32_t ret;
	int32_t image_fd;
	char dump_out_filename[DUMP_OUT_FILE_STR_MAX_LENGTH] = {'\0'};

	snprintf(dump_out_filename, DUMP_OUT_FILE_STR_MAX_LENGTH, "img_rgba8888_%dx%d.rgba", win_w, win_h);

	eglWaitNative(EGL_CORE_NATIVE_ENGINE);	// 等待原生窗口系统的渲染完成


	front_bo = gbm_surface_lock_front_buffer((struct gbm_surface *)egl_window);
	ASSERT_TRUE_WITH_RETURN_VAL(front_bo != nullptr, -DUMP_FAILED);

	image_fd = gbm_bo_get_fd_for_plane(front_bo, 0);
	ASSERT_TRUE_WITH_RETURN_VAL(image_fd >= 0, -DUMP_FAILED);

	
	// 使用mmap将显存里的地址映射到内存地址
	std::cout << "front_bo.w=" << gbm_bo_get_width(front_bo) << " front_bo.h=" << gbm_bo_get_height(front_bo) << std::endl;
	uint64_t size = gbm_bo_get_width(front_bo) * gbm_bo_get_height(front_bo) * 4;
	void* mapped_data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, image_fd, 0);
	if (mapped_data == MAP_FAILED) {
		// 映射失败，错误处理
		close(image_fd);
		gbm_surface_release_buffer((struct gbm_surface *)egl_window, front_bo);
		std::cout << "dump_one_image(): mmap() failed." << std::endl;
		return DUMP_FAILED;
	}

	// 写入磁盘文件
	ret = disp_save_file_mod(dump_out_filename, mapped_data, &size);
	ASSERT_TRUE_WITH_RETURN_VAL(ret == 0, -DUMP_FAILED);

	// 访问完数据后，记得解除映射并关闭文件描述符
	munmap(mapped_data, size);
	close(image_fd);
	
	// 释放前端缓冲区
	gbm_surface_release_buffer((struct gbm_surface *)egl_window, front_bo);
	return DUMP_SUCCESS;
}

int32_t egl_gbm_drm_init::dump_one_image_gl(std::vector<GLubyte> &pixels)
{
	std::ofstream file("img_rgba8888_1920x1080.rgba", std::ios::binary | std::ios::trunc);
    if (file.is_open()) {
        file.write(reinterpret_cast<char*>(pixels.data()), pixels.size());
        file.close();
        std::cout << "Image data saved to img_bgra8888_1920x1080.rgba" << std::endl;

		// test output 

		FILE* fp = nullptr;
		uint64_t written;
	
		std::cout << "dump file check:" << std::endl;
		fp = fopen("img_rgba8888_1920x1080.rgba", "rb");
		if(fp == nullptr){
			printf("open(%s) fail", "img_bgra8888_1920x1080.rgba");
			return -1;
		}

		const int numBytesToRead = 16;
		char buffer[numBytesToRead];
		size_t byteRead = fread(buffer, 1, numBytesToRead, fp);
		if(byteRead != numBytesToRead){
			std::cerr << "Error reading file" << std::endl;
			return -1;
		}
		fclose(fp);

		for(int i = 0; i < numBytesToRead; i++){

			if(i % 4 == 0)
				std::cout << std::endl;

			printf(" %d ", (u_char)buffer[i]);
		}
		std::cout << std::endl;

    } else {
        std::cerr << "Failed to open file for writing" << std::endl;
    }

    return 0;
}

int32_t egl_gbm_drm_init::init_drm()
{
	uint32_t bestArea(0);
	drmModeModeInfo* curMode;

	resources_ = drmModeGetResources(drm_fd);
	if (!resources_) {
		std::cout << "Failed to get drm resources" << std::endl;
		return -INIT_FAILED;
	}

	for (int32_t c = 0; c < resources_->count_connectors; c++) {
		connector_ = drmModeGetConnector(drm_fd, resources_->connectors[c]);
		if (DRM_MODE_CONNECTED == connector_->connection) {
			break;
		}
		drmModeFreeConnector(connector_);
		connector_ = 0;
	}

	if (!connector_) {
		std::cout << "Failed to get drm connector" << std::endl;
		return -INIT_FAILED;
	}

	for (int32_t m = 0; m < connector_->count_modes; m++) {
		curMode = &connector_->modes[m];
		uint32_t curArea = curMode->hdisplay * curMode->vdisplay;
		if (curArea > bestArea) {
			bestArea = curArea;

			if (curMode->hdisplay >= win_w && curMode->vdisplay >= win_h) {
				mode_ = curMode;
				break;
			}
		}
	}

	if (!mode_) {
		std::cout << "Failed to find a suitable mode" << std::endl;
		return -INIT_FAILED;
	}

	for (int32_t e = 0; e < resources_->count_encoders; e++) {
		bool found = false;
		encoder_ = drmModeGetEncoder(drm_fd, resources_->encoders[e]);
		for (int32_t ce = 0; ce < connector_->count_encoders; ce++) {
			if (encoder_ && encoder_->encoder_id == connector_->encoders[ce]) {
				found = true;
				break;
			}
		}
		if (found) {
			break;
		}
		drmModeFreeEncoder(encoder_);
		encoder_ = 0;
	}

	if (!encoder_) {
		for (int32_t e = 0; e < connector_->count_encoders; e++) {
			encoder_ = drmModeGetEncoder(drm_fd, connector_->encoders[e]);
			for (int32_t c = 0; c < resources_->count_crtcs; c++) {
				if (encoder_->possible_crtcs & (1 << c)) {
					encoder_->crtc_id = resources_->crtcs[c];
					break;
				}
			}
			if (encoder_->crtc_id) {
				break;
			}

			drmModeFreeEncoder(encoder_);
			encoder_ = 0;
		}
	}

	if (!encoder_) {
		std::cout << "Failed to find a suitable encoder" << std::endl;
		return -INIT_FAILED;
	}

	crtc_ = drmModeGetCrtc(drm_fd, encoder_->crtc_id);
	if (!crtc_) {
		for (int32_t c = 0; c < resources_->count_crtcs; c++) {
			if (encoder_->possible_crtcs & (1 << c)) {
				encoder_->crtc_id = resources_->crtcs[c];
				break;
			}
		}
	}

	drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
	drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1);

	return INIT_SUCCESS;
}

egl_gbm_drm_init::DRMFBState* egl_gbm_drm_init::fb_get_from_bo(gbm_bo* bo)
{
	DRMFBState* fb = reinterpret_cast<DRMFBState*>(gbm_bo_get_user_data(bo));
	uint32_t width = gbm_bo_get_width(bo);
	uint32_t height = gbm_bo_get_height(bo);
	uint32_t handles[4] = { 0, }, strides[4] = { 0, }, offsets[4] = { 0, };
	uint32_t format = gbm_bo_get_format(bo);
	uint32_t fb_id(0);
	int32_t status;
	int32_t i;

	if (!bo) {
		return NULL;
	}

	if (fb) {
		return fb;
	}

	for (i = 0; i < gbm_bo_get_plane_count(bo); i++) {
		handles[i] = gbm_bo_get_handle_for_plane(bo, i).u32;
		strides[i] = gbm_bo_get_stride_for_plane(bo, i);
		offsets[i] = gbm_bo_get_offset(bo, i);
	}

	status = drmModeAddFB2(drm_fd, width, height, format, handles,
                           strides, offsets, &fb_id, 0);
	if (status < 0) {
		std::cout << "Failed to create FB" << std::endl;
		return nullptr;
	}

	fb = new DRMFBState();
	fb->fd = drm_fd;
	fb->bo = bo;
	fb->fb_id = fb_id;

	gbm_bo_set_user_data(bo, fb, fb_destroy_callback);
	return fb;
}

int32_t egl_gbm_drm_init::flip()
{
	int32_t ret = RUN_SUCCESS;
	DRMFBState* pending_fb = nullptr;
	int32_t status;

	if (enable_drm_ != true) {
		return RUN_SUCCESS;
	}

	if (drmSetMaster(drm_fd) < 0) {
		std::cout << "Failed to become DRM master" << std::endl;
		return -RUN_FAILED;
	}

	if (pending_bo_)
		gbm_surface_release_buffer((struct gbm_surface *)egl_window, pending_bo_);
	pending_bo_ = gbm_surface_lock_front_buffer((struct gbm_surface *)egl_window);

    	pending_fb = fb_get_from_bo(pending_bo_);

	if (!pending_bo_ || !pending_fb) {
		std::cout << "Failed to get gbm front buffer" << std::endl;
		return -RUN_FAILED;
	}

        check_for_page_flip(0);

	/* If a flip is not in progress we can schedule another one. */
	if (!flipped_bo_) {
		if (!crtc_set_) {
			drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm_fd);
			uint32_t property_active_id, property_mode_id, property_crtc_id;
			uint32_t blob_id;
			drmModeObjectProperties *props;
			drmModeAtomicReq *req;

			// plane 0 supports yuv format
			plane_id_ = plane_res->planes[getDrmPlaneIndex()];
			drmModeFreePlaneResources(plane_res);

			props = drmModeObjectGetProperties(drm_fd, connector_->connector_id, DRM_MODE_OBJECT_CONNECTOR);
			property_crtc_id = get_drm_property_id(drm_fd, props, "CRTC_ID");
			drmModeFreeObjectProperties(props);

			props = drmModeObjectGetProperties(drm_fd, encoder_->crtc_id, DRM_MODE_OBJECT_CRTC);
			property_active_id = get_drm_property_id(drm_fd, props, "ACTIVE");
			property_mode_id  = get_drm_property_id(drm_fd, props, "MODE_ID");
			drmModeFreeObjectProperties(props);

			drmModeCreatePropertyBlob(drm_fd, mode_, sizeof(*mode_), &blob_id);

			req = drmModeAtomicAlloc();
			if (req == nullptr) {
				std::cout << "drmModeAtomicAlloc failed, exiting..." << std::endl;
				exit(EXIT_FAILURE);
			}
			drmModeAtomicAddProperty(req, encoder_->crtc_id, property_active_id, 1);
			drmModeAtomicAddProperty(req, encoder_->crtc_id, property_mode_id, blob_id);
			drmModeAtomicAddProperty(req, connector_->connector_id, property_crtc_id, encoder_->crtc_id);
			status = drmModeAtomicCommit(drm_fd, req, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL);
			drmModeAtomicFree(req);

			if (status >= 0) {
				crtc_set_ = true;

				status = drmModeSetPlane(drm_fd, plane_id_, encoder_->crtc_id, pending_fb->fb_id, 0,
							 0, 0, win_w, win_h,
							 0, 0, uint32_t(win_w) << 16, uint32_t(win_h) << 16);
				if (status < 0) {
					std::cout << "Failed to set plane_id_: " << status << std::endl;
					return -RUN_FAILED;
				}

				if (presented_bo_) {
					gbm_surface_release_buffer((struct gbm_surface *)egl_window, presented_bo_);
				}

				presented_bo_ = pending_bo_;
				flipped_bo_ = nullptr;
				pending_bo_ = nullptr;
			} else {
				std::cout << "Failed to set crtc: " << status << std::endl;
				return -RUN_FAILED;
			}

			return RUN_SUCCESS;
		}

		status = drmModeSetPlane(drm_fd, plane_id_, encoder_->crtc_id, pending_fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT,
						 0, 0, win_w, win_h,
						 0, 0, uint32_t(win_w) << 16, uint32_t(win_h) << 16);
		if (status < 0) {
			std::cout << "Failed to enqueue page flip, status: " << status << std::endl;
			return -RUN_FAILED;
		}

		flipped_bo_ = pending_bo_;
		pending_bo_ = nullptr;
	}

	while (!gbm_surface_has_free_buffers((struct gbm_surface *)egl_window) && check_for_page_flip(-1) >= 0) {
		continue;
	}

   	return RUN_SUCCESS;
}

int32_t egl_gbm_drm_init::check_for_page_flip(int32_t timeout_ms)
{
	fd_set fds;
	drmEventContext evCtx;
	int32_t status;
	struct timeval timeout = {0, timeout_ms * 1000};

	FD_ZERO(&fds);
	FD_SET(drm_fd, &fds);

	memset(&evCtx, 0, sizeof(evCtx));
	evCtx.version = 2;
	evCtx.page_flip_handler = page_flip_handler;

	status = select(drm_fd + 1, &fds, 0, 0, timeout_ms < 0 ? nullptr : &timeout);
	if (status == 1)
		drmHandleEvent(drm_fd, &evCtx);

	return status;
}

void egl_gbm_drm_init::fb_destroy_callback(gbm_bo* bo, void* data)
{
	DRMFBState* fb = reinterpret_cast<DRMFBState*>(data);

	if (fb && fb->fb_id) {
		drmModeRmFB(fb->fd, fb->fb_id);
	}
	delete fb;
}

void egl_gbm_drm_init::page_flip_handler(int32_t/*  fd */, uint32_t /* frame */, uint32_t /* sec */, uint32_t /* usec */, void* data)
{
	egl_gbm_drm_init* state = reinterpret_cast<egl_gbm_drm_init*>(user_data_);

	if (state->presented_bo_) {
		gbm_surface_release_buffer((struct gbm_surface *)state->egl_window, state->presented_bo_);
	}
	state->presented_bo_ = state->flipped_bo_;
	state->flipped_bo_ = nullptr;
}